using System;
using System.Diagnostics;
using System.Collections.Generic;

namespace OpenRay
{
	public class LensCamera : PinholeCamera
	{
		private readonly float lensAperture, lensFocalLength;
		private readonly int samples = 4;
		
		public LensCamera (IScene s, int x, int y, float imagePlaneDistance, float imagePlaneX, float imagePlaneY, float aperture, float focalLength)
			: base( s, x, y, imagePlaneDistance, imagePlaneX, imagePlaneY )
		{
			Debug.Assert(aperture >= 0f);
			
			lensAperture = aperture;
			lensFocalLength = focalLength;
		}
		
		private void concentricCircleSample( ref float dx, ref float dy )
		{
			float r, theta;
			
			float sx = dx * 2f - 1f;
			float sy = dy * 2f - 1f;
			
		    if ( sx >= -sy ) {
		        if ( sx > sy ) {
		            r = sx;
		            if ( sy > 0.0f ) {
		                theta = sy / r;
		            } else {
		                theta = 8.0f + sy / r;
		            }
		        } else {
		            r = sy;
		            theta = 2.0f - sx / r;
		        }
		    } else {
		        if ( sx <= sy ) {
		            r = -sx;
		            theta = 4.0f - sy / r;
		        } else {
		            r = -sy;
		            theta = 6.0f + sx / r;
		        }
		    }
			
			theta *= (float)Math.PI / 4.0f;
			
			dx = r * (float)Math.Cos(theta);
			dy = r * (float)Math.Sin(theta);
		}
		
		public override FrameBuffer Render()
		{
			if ( lensAperture == 0f )
				return base.Render();
			
			//float focusDistance = lensFocalLength / ( 1.0f - ( lensFocalLength / imagePlaneDistance ) );
			float focusDistance = Math.Abs( ( imagePlaneDistance * lensFocalLength ) / ( imagePlaneDistance - lensFocalLength ) );
			
			Point LensLocation = new Point(0f, 0f, 0f);
			Point imagePlane = new Point( -imageX / 2f, -imageY / 2f, imagePlaneDistance );
			
			Vector dx = new Vector( imageX, 0f, 0f ) * (1.0f / (float)frame.Width);
			Vector dy = new Vector( 0f, imageY, 0f) * (1.0f / (float)frame.Height);
			
			for ( int y = 0; y < frame.Height; y++ )
			{
				for( int x = 0; x < frame.Width; x++ )
				{
					Point pixelCenter = imagePlane + dx * ((float)x + 0.5f) + dy * ((float)y + 0.5f);
					Vector centerDirection = LensLocation - pixelCenter;
					
					Point focalPoint = LensLocation + centerDirection * (float)Math.Abs( focusDistance / imagePlaneDistance );
					
					Color temp = new Color(0, 0, 0, 0);
					
					for ( int u = 0; u < samples; u++ )
					{
						for ( int v = 0; v < samples; v++ )
						{
                    		float uLens= ( 0.5f + (float)u ) / (float)samples;
                    		float vLens = ( 0.5f + (float)v ) / (float)samples;

                    		concentricCircleSample( ref uLens, ref vLens );
                    		uLens *= lensAperture;
                    		vLens *= lensAperture;

							Point origin = new Point( uLens, vLens, 0f );
							Vector direction = focalPoint - origin; //new Vector( focalPoint.X, focalPoint.Y, focalPoint.Z );
							
							Ray r = new Ray(origin, direction);
							
							IList<PrimitiveHit> hits = scene.Trace(r, maxDepth);
							
							if ( hits.Count > 0 )
							{
								temp += hits[0].Shade(r);
							}
						}
					}
					
					temp *= 1f / (float)(samples * samples);
					frame.SetPixelArgb( frame.Width - 1 - x, y, temp.ToArgb() );
				}
			}
			
			return frame;
		}
	}
}

